package org.zjvis.datascience.common.graph.algo;

import org.zjvis.datascience.common.graph.util.GraphUtil;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @description PageRank
 * @date 2021-12-29
 */
public class PageRank {
    private final Double EPS = 0.001D;
    private final Double D = 0.85D;
    private final Double Wuv = 1.0D;
    private final Integer ITERATION = 50;
    private final Double INIT_VALUE = 1.0D;
    private Integer N;
    private Double COMMON;
    private Map<Object, Double> pageRankMap;


    private Map<Object, List<Object>> neighborMap;
    private Map<Object, Long> outDegreeMap;

    public PageRank(Map<Object, List<Object>> neighborMap, Map<Object, Long> outDegreeMap) {
        this.neighborMap = neighborMap;
        this.outDegreeMap = outDegreeMap;
        this.N = neighborMap.size();
        this.COMMON = (1 - D) / N;
        this.pageRankMap = new HashMap<>();
        for (Object id: outDegreeMap.keySet()) {
            pageRankMap.put(id, INIT_VALUE);
        }
    }

    public void execute() {
        Integer iter = 0;
        boolean done = false;
        while (!done) {
            for (Object id: outDegreeMap.keySet()) {
                List<Object> neighbors = neighborMap.get(id);
                Double accumulate = 0.0D;
                for (Object neighborId: neighbors) {
                    Long dv = outDegreeMap.get(neighborId);
                    accumulate += dv == 0 ? 0 : pageRankMap.get(neighborId) / dv;
                }
                Double newPR = COMMON + D * accumulate;
                if (iter < ITERATION && Math.abs(newPR - pageRankMap.get(id)) >= EPS) {
                    pageRankMap.put(id, newPR);
                }
                else {
                    done = true;
                }
            }
            iter ++;
            if (iter >= ITERATION) {
                done = true;
            }
        }

        //
        Double sum = pageRankMap.values().stream().mapToDouble(v-> v).sum();
        pageRankMap.replaceAll((k, v) -> GraphUtil.round2DecimalDouble(pageRankMap.get(k) / sum));
    }

    public Map<Object, Double> getPageRankMap() {
        return pageRankMap;
    }
}
